/**
 *
 * The MIT License
 *
 * Copyright (c) 2008 Board of Trustees, Leland Stanford Jr. University. All rights reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:

 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.

 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
package org.macdadi.ks.client.views;

import com.google.gwt.core.client.GWT;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.*;
import org.macdadi.core.client.auth.AuthService;
import org.macdadi.core.client.auth.AuthorizationException;
import org.macdadi.core.client.domain.*;
import org.macdadi.ks.client.View;
import org.macdadi.ks.client.ViewContainer;

/**
 * @author Ben Suter <ben.suter@gmail.com>
 */
public class Impacts extends View implements ClickListener
{
    protected FlexTable table;
    protected ProjectInfo project;
    protected boolean allowUpdate = false;
    
    public static ViewInfo init()
    {
        String info = "<p>Evaluate the impact of each option on each goal</p>";

        return new ViewInfo("Impacts", info)
        {
            public View createInstance(ViewContainer container)
            {
                return new Impacts(container);
            }

            public String getColor()
            {
                return "#fe9915";
            }
        };
    }

    public Impacts(ViewContainer container)
    {
        this.container = container;
        
        VerticalPanel mainPanel = new VerticalPanel();
        mainPanel.setStyleName("macdadi-Impacts");
        mainPanel.setWidth("100%");

        table = new FlexTable();
        mainPanel.add(table);

        initWidget(mainPanel);
    }

    public void onShow()
    {
        populateTable();

        allowUpdate = container.getCurrentUser().isAuthorizedOperation(project.getId(), AuthService.TARGET_IMPACTS, AuthService.OPERATION_UPDATE);
    }

    public void onHide()
    {
        table.clear();
        allowUpdate = false;
    }

    protected void populateTable()
    {
        project = container.getCurrentProject();

        DataService.App.getInstance().getImpacts(project.getId(), container.getCurrentAuthToken(), new AsyncCallback()
        {
            public void onFailure(Throwable caught)
            {
                GWT.log("Failed to populate the impacts table", caught);
                if (caught instanceof AuthorizationException) {
                    // TODO: do not use Window.alert for error feedback
                    Window.alert("Please login to view goals for " + project.getName());
                }
            }

            public void onSuccess(Object result)
            {
                ImpactMatrix matrix = (ImpactMatrix) result;

                /* Create options header row */
                Option[] options = matrix.getOptions();
                for (int i = 0; i < options.length; i++) {
                    /* The header row displays the options, from the second heading on*/
                    table.setText(0, i + 1, options[i].getName());
                    table.getFlexCellFormatter().setStyleName(0, i + 1, "impact-option-header");
                }

                /* Create goals header column */
                WeightedGoal[] goals = matrix.getGoals();
                for (int i = 0; i < goals.length; i++) {
                    /* The left-most column displays the Goals, from the second row on*/
                    table.setText(i + 1, 0, goals[i].getName());
                    table.getFlexCellFormatter().setStyleName(i + 1, 0, "impact-goal-header");
                }

                /* Fill in cells with the impact values */
                WeightedGoal g;
                for (int i = 0; i < goals.length; i++) {
                    g = goals[i];
                    for ( int j = 0; j < options.length; j++) {
                        setImpactTableCell(i + 1, j + 1, matrix.getImpact(g, options[j]));
                    }
                }

            }
        });
    }

    // TODO: table sorting: click on option, sort so that values asc/desc for that option
    // TODO: table sorting: click on goal, sort so that values asc/desc L-R for that goal (re-order columns)

    protected void setImpactTableCell(int row, int col, Impact impact)
    {
        // improve this, use CSS class instead of straight color mapping here in code
        // also, apply to entire cell, not just to span of content

        table.setWidget(row, col, new ImpactCell(impact, this));
        table.getFlexCellFormatter().setStyleName(row, col, getValueStyle(impact.getValue()));
    }

    protected String getValueColor(int value) {
        switch (value) {
            case 0:
                return "#ffffff";
            case -1:
            case -2:
            case -3:
                return "#ff0000";
            case 1:
            case 2:
            case 3:
                return "#00ff00";
            default:
                return "#ffffff";
        }        
    }

    protected String getValueStyle(int value) {
          switch (value) {
              case 0:
                  return "impact-zero";
              case -1:
              case -2:
              case -3:
                  return "impact-negative";
              case 1:
              case 2:
              case 3:
                  return "impact-positive";
              default:
                  return "";
          }
      }

    public void onClick(Widget sender) {
        if ( ! allowUpdate )
            return;
        
        if (sender instanceof ImpactCell) {
            if (cellEditPanel == null) {
                cellEditPanel = new ImpactCellEditor();
            }
            
            ImpactCell cell = (ImpactCell) sender;
            cellEditPanel.editImpact(cell.getImpact(), cell.getAbsoluteLeft() + 30, cell.getAbsoluteTop());
        }
    }

    protected ImpactCellEditor cellEditPanel;

    class ImpactCellEditor extends DialogBox implements AsyncCallback
    {
        protected Impact impact;
        protected ListBox choices;

        public ImpactCellEditor() {
            super(true, true);
            setText("Define the Impact");

            /* A panel for editing the impact value */
            VerticalPanel vp = new VerticalPanel();

            choices = new ListBox();
            for ( int i = 3; i > -4; i-- ) {
                choices.addItem(Integer.toString(i));
            }
            vp.add(choices);

            Button btnAdd = new Button("Apply");
            btnAdd.addClickListener(new ClickListener() {
                public void onClick(Widget sender) {
                    int newValue;
                    newValue = Integer.parseInt(choices.getValue(choices.getSelectedIndex()));
                    updateImpact(newValue);
                }
            });
            vp.add(btnAdd);

            setWidget(vp);    
        }

        public void editImpact(Impact impact, int x, int y) {
            this.impact = impact;
            choices.setSelectedIndex(3 - impact.getValue());
            setPopupPosition(x + 30, y);
            show();
        }

        public void stopEditing() {
            hide();
            impact = null;
        }

        protected void updateImpact(int newValue) {
            if ( impact == null )
                return;
            
            // TODO: implement in full
            //Window.alert("Changing value to " + newValue + " for goal " + impact.getGoal().getId() + " and option " + impact.getOption().getId());

            DataWriteService.App.getInstance().updateImpact(project.getId(), impact.getGoal().getId(), impact.getOption().getId(), newValue, container.getCurrentAuthToken(), this);
        }

        /**
         * Displays an error message
         *
         * @param caught  the exception
         */
        public void onFailure(Throwable caught) {
            // TODO: display a message to the user
            GWT.log("The desired change did not succeed. A problem occurred: " + caught.getMessage(), caught);
        }

        /*
         * In the case of success, the editor is closed and the onSuccess command
         * is executed. Otherwise we log an error.
         *
         * @param result true if the impact was updated successfully
         */
        public void onSuccess(Object result) {
            if (((Boolean) result).booleanValue()) {
                stopEditing();
                table.clear();
                populateTable();
            } else {
                // TODO: show feedback to user
                GWT.log("The desired change did not succeed", null);
            }
        }

    }

    class ImpactCell extends HTML
    {
        protected Impact impact;

        public ImpactCell(Impact impact, ClickListener listener) {
            super();
            this.impact = impact;
            buildSelf();
            addClickListener(listener);
        }

        protected void buildSelf() {
            setHTML(Integer.toString(impact.getValue()));
        }

        public Impact getImpact() {
            return impact;
        }
    }
}